Technical Note TN2091
Device input using the HAL Output Audio Unit

このテクニカルノートでは、ハードウェア抽象化層の AudioOutputUnit (AUHAL) を使ってオーディオデバイスから入力を取得する方法について説明します。AUHAL を使って、オーディオデバイスでよく行われる入出力処理を簡素化できます。





アプリケーションは、ハードウェア抽象化層(HAL)の AudioOutputUnit を使って、単一のオーディオデバイスとインタフェースをとることができます。AudioOutputUnit (AUHAL) ユニットは、<CoreAudio/AudioHardware.h> で定義されている AudioDevice オブジェクトの上層にあります。AUHAL は、オーディオデバイスの入力と出力に使用できます。

AUHAL を使ってオーディオデバイスから入力を取り込むには、以下の手順に従ってください。

1. AUHAL を開きます。

2. AUHAL の入力を使用可能にします。

3. デフォルトの入力デバイスを AUHAL の現在の入力デバイスに設定します。

4. デバイスフォーマットを取得し、希望のオーディオフォーマットを指定します。

5. 入力コールバックを作成し、AUHAL に登録します。

6. 必要なバッファを割り当てます。

7. AUHAL を初期化して開始します。

これらの手順について、このテクニカルノートで詳しく説明します。

AudioOutputUnit の作成

まず、他の Audio Unit を取得する場合と同様に、Component Description を使って AudioOutputUnit を取得する必要があります。

リスト 1. AudioOutputUnit を開く方法

    Component comp;
    ComponentDescription desc;
    
    // 数種類の Audio Unit がある。
    // いくつかのオーディオユニットは入力、ミキサー、DSP ユニットとして機能する。
    // 一覧については、AUComponent.h を参照。
    desc.componentType = kAudioUnitType_Output;

    // すべてのコンポーネントは subType を持っており、これがコンポーネントの機能を
    // 明確に記述する。
    desc.componentSubType = kAudioUnitSubType_HALOutput;

    // AUComponent.h のすべての Audio Unit は、
    //"kAudioUnitManufacturer_Apple" を Manufacturer として使用する必要がある。
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;
     
    // desc のスペックに合ったコンポーネントを検出する。
    comp = FindNextComponent(NULL, &desc);
    if (comp == NULL) exit (-1);
   
    // コンポーネントの提供するサービスにアクセスする。
    OpenAComponent(comp, &InputUnit);

先頭に戻る

Audio Unit 接続の概要

Audio Unit は、異なる 2 つの Audio Unit 間の直接接続の概念を具現化しています。Audio Unit は、データを生成するように要求されると、コールバック関数または接続されている別の Audio Unit からデータを受け取ることができます。接続の例として、A1 と A2 という 2 つの Audio Unit があれば、A1 を A2 に接続します (A1-->A2)。A2 がデータを生成するように要求されると、データストリームは基本的に A1 から A2 へ「プル」されて処理されます。そのため、Audio Unit 間の接続は、同じオーディオストリームフォーマットを共有している必要があります。Audio Unit と接続の詳細については、「Audio and MIDI on Mac OS X」 を参照してください。

The signal flow of the AUHAL図 1: AUHAL のシグナルフロー

図 1 は、オーディオデバイスとアプリケーションの間のオーディオデータの流れを示しています。アプリケーションは、処理を単純化するために、Audio Unit を AUHAL のいずれかの要素(バス)に接続することができます。つまり、デバイスにオーディオを出力するソースとして Audio Unit を使う場合は、次の接続を使用します。

表 1. デバイスへのオーディオの出力

ソース出力先
ソースユニット(出力範囲、出力要素)AUHAL(入力範囲、要素 0)

オーディオデバイスの入力データを取得したい場合は、次のように接続します。

表 2. デバイスへの音声の取り込み

ソース出力先
AUHAL(出力範囲、要素 1)出力先ユニット(入力範囲、入力要素)

もちろん、入出力を提供する内蔵オーディオデバイスのようなデバイスについては、次の接続を作成することで、ソフトウェアプレイスルーのメカニズムを確立することができます。

表 3. 簡単なソフトウェアプレイスルー

ソース出力先
AUHAL(出力範囲、要素 1)AUHAL(入力範囲、要素 0)

入出力の間に 1 つ以上のオーディオユニットを挿入することにより、入力から出力まで、オーディオへの必要な処理操作を何度でも実行できます。ここで、マルチバンドのコンプレッサ Audio Unit を使って内蔵オーディオ入力を処理する例を挙げてみましょう。これを行うには、次の接続を作成します。

表 4. マルチバンドのコンプレッサ Audio Unit による内蔵オーディオ入力の処理

ソース出力先
AUHAL(出力範囲、要素 1)マルチバンドコンプレッサ(入力範囲、要素 0)
マルチバンドコンプレッサ(出力範囲、要素 0)AUHAL(入力範囲、要素 0)

(AudioToolbox.framework の)AUGraph API は、これらの接続を管理できます。AUGraph は、一組の Audio Unit とそれらの接続の高レベルの表現です。この便利な API の使い方に関する詳細は、「Audio and MIDI on Mac OS X -May 2001」 の第 4 章 および 「Core Audio Overview」 を参照してください。

2 つの別々のオーディオデバイスを使う場合は、2 つの AUHAL が必要となります。しかし、それぞれの AUHAL は個別の I/O プロセスで実行するため、2 つの AUHAL の間に直接の接続を確立できません。通知メカニズムを使用して、出力デバイスに対してデータが到着したことを通知し、データを渡す必要があります。

注意: AUGraph ごとに使用できる AudioOutputUnit は 1 つのみです。

先頭に戻る

IO の有効化

AUHAL オブジェクトを作成したら、デバイス入力を取得するために、Audio Unit の入力範囲にある IO を使用可能にする必要があります。入力は、AUHAL の要素 1 にある kAudioOutputUnitProperty_EnableIO プロパティで明示的に使用可能にする必要があります。AUHAL は入出力の両方に使用できるため、この例では出力範囲の IO を使用不可にする必要があります。

リスト 2. AudioOutputUnit の入力の有効化と出力の無効化

      UInt32 enableIO;
      UInt32 size=0;

      // AudioUnitSetProperty を使用する場合は、メソッドの 4 番目のパラメータが
      //AudioUnitElement を参照する。AudioOutputUnit を使用する場合は、
      // 入力要素が '1' になり、出力要素が '0' になる。


      enableIO = 1;
      AudioUnitSetProperty(InputUnit,
                kAudioOutputUnitProperty_EnableIO,
                kAudioUnitScope_Input,
                1, // 入力要素
                &enableIO,
                sizeof(enableIO));

      enableIO = 0;
      AudioUnitSetProperty(InputUnit,
                kAudioOutputUnitProperty_EnableIO,
                kAudioUnitScope_Output,
                0, // 出力要素
                &enableIO,
                sizeof(enableIO));

先頭に戻る

AudioOutputUnit の現在のデバイスの設定

AUHAL には、インタフェースをとるデバイスがなければなりません。この例では、現在のデバイスとしてシステムのデフォルト入力デバイスを選択します。AudioHardwareGetPropertykAudioHardwarePropertyDefaultInputDevice パラメータ の組み合わせで、ユーザによって選択された現在の入力デバイスを取得します。AudioDeviceID を取得した後、kAudioOutputUnitProperty_CurrentDeviceAudioUnitSetProperty パラメータにより、オーディオデバイスを Audio Unit の現在のデバイスとして設定できます。デバイスを AUHAL に設定できるのは、入出力を有効にしてからのみである点を忘れないでください。

リスト 3. AudioOutputUnit の現在のデバイスをデフォルト入力デバイスに設定する方法

OSStatus SetDefaultInputDeviceAsCurrent(){
    UInt32 size;
    OSStatus err =noErr;
    size = sizeof(AudioDeviceID);

    AudioDeviceID inputDevice;
    err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice,
                                                  &size,
                                                  &inputDevice);

    if (err)
        return err;

    err =AudioUnitSetProperty(InputUnit,
                         kAudioOutputUnitProperty_CurrentDevice,
                         kAudioUnitScope_Global,
                         0,
                         &inputDevice,
                         sizeof(inputDevice));
  
   return err;

}

先頭に戻る

オーディオデータのフォーマットについて

AUHAL は、入出力ともに、デバイスのオーディオデータストリームを単一のインターリーブされていないストリームにフラット化します。AUHAL には、この変換を実行する AudioConverter が組み込まれています。AUHAL はフラット化されたデバイスフォーマットとクライアントの希望フォーマットを比較して、どの種類の AudioConverter が必要であるかを判断します。デバイスフォーマットとクライアントフォーマットのどちらかを設定し直すと、通常は中断が生じるため、AUHAL は新しい AudioConverter を設定する必要があります。デバイスフォーマットと希望のフォーマットのチャネルが 1:1 の比率でない場合、AUHAL ユニットはチャネルマップを使って、ユーザに提示するチャネルを決定することができます。最後に、デバイスのサンプルレートは、希望のサンプルレートと一致している必要があります。

オーディオデバイスにデータを出力するには、フォーマットを必ず AUHAL の要素 0 の出力範囲で明示します。オーディオデバイスフォーマットを取得するには、AudioUnitGetProperty と定数 kAudioUnitProperty_StreamFormat を使います。この情報は取得できますが、決して書き込み可能ではありません。ユーザはこれらの設定自体を明示的に変更する必要があります。

デバイスから入力を取得するには、デバイスフォーマットを必ず AUHAL の要素 1 の入力範囲で明示します。このため、希望のフォーマットは AUHAL の 要素 1 の出力範囲に設定する必要があります。内部の AudioConverter は、あらゆる「単純な」変換を処理できます。つまり、通常はクライアントが PCM フォーマットの任意のバリエーションを指定できるということです。最後に、デバイスのサンプルレートは、希望のサンプルレートと一致している必要があります。サンプルレートを変換する必要がある場合は、別の AudioConverter を使って、独立したスレッドで入力をバッファリングして、データを変換できます。

リスト 4. 希望する「入力」フォーマットの設定

    CAStreamBasicDescription DeviceFormat;
    CAStreamBasicDescription DesiredFormat;
   //CAStreamBasicDescriptions を「裸」の
   //AudioStreamBasicDescriptions の代わりに使うことでエラーを最小限にします。
   //CAStreamBasicDescription.h は、CoreAudio SDK に含まれています。

    UInt32 size = sizeof(CAStreamBasicDescription);

    // 入力デバイスフォーマットを取得する
    AudioUnitGetProperty (InputUnit,
                                   kAudioUnitProperty_StreamFormat,
                                   kAudioUnitScope_Input,
                                   1,
                                   &DeviceFormat,
                                   &size);

    // 希望のフォーマットをデバイスのサンプルレートに設定する
    DesiredFormat.mSampleRate =       DeviceFormat.mSampleRate;

    // フォーマットを出力範囲に設定する
    AudioUnitSetProperty(
                            InputUnit, 
                            kAudioUnitProperty_StreamFormat,
                            kAudioUnitScope_Output,
                            1,
                            &DesiredFormat,
                            sizeof(CAStreamBasicDescription);

先頭に戻る

チャネルマッピング

オーディオデバイスのチャネルと希望フォーマットのチャネルのデータフォーマットが 1:1 の比率に対応していない場合は、チャネルマッピングが必要です。チャンルマッピングでは、Audio Unit がどんなデバイスチャネルと対話するかを指定します。これを設定する必要があるのは、デフォルトのマッピング以外の設定を使う予定がある場合に限られます。

表 5. デフォルトのチャネルマップ(チャネルマッピングは不要)

デバイスチャネル使用チャネル
00
11
22
......

たとえば、入力に使用する 4 チャネルのデバイスがあり、ステレオ入力としてデバイスの 2 または 3 チャネルのみが必要であったとします。必要なチャネルを AUHAL のチャネルに割り当てる(マップする)必要があります。AUHAL のチャネルマップを作成するには、マップの「出力先」ごとに SInt32 の配列を作成する必要があります。Sint32 の配列の各要素は、出力先に送られるソースチャネルのインデックス、または "ソースなし" を意味する -1 を参照します。この例では、2 つの要素の配列を作成し、その値を -1 に初期設定します。マップしたいチャネルには、チャネルマップ配列の要素の値を 2 と 3 に設定します。その結果、チャネルマップは [2,3] になります。

表 6. 4 -> 2 チャネルマップ

デバイスチャネル使用チャネル
20
31

リスト 5. 入力用の 4 -> 2 チャネルマッピングの例

 
    SInt32 *channelMap =NULL;
    UInt32 numOfChannels = DesiredFormat.mChannelsPerFrame;  //2 チャネル
    UInt32 mapSize = numOfChannels *sizeof(SInt32);
    channelMap = (SInt32 *)malloc(size);
    
    // 必要な入力の各チャネルに、デバイスの出力チャネルから
    // チャネルをマップする。
    for(UInt32 i=0;i<numOfChannels;i++)
    {
               channelMap[i]=-1;
     }
     //channelMap[desiredInputChannel] = deviceOutputChannel;
     channelMap[0] = 2;
     channelMap[1] = 3;
     AudioUnitSetProperty(InputUnit,
                                        kAudioOutputUnitProperty_ChannelMap,
                                        kAudioUnitScope_Output,
                                        1,
                                        channelMap,
                                        size);
     free(channelMap);

先頭に戻る

AudioOutputUnit の入力プロシージャの作成

次に、AUHAL の入力プロシージャを登録する必要があります。AUHAL が入力デバイスから新しいデータを受け取ると、このプロシージャが呼び出されます。

リスト 6. AudioOutputUnit の入力プロシージャの作成

void MyInputCallbackSetup()
{
    AURenderCallbackStruct input;
    input.inputProc = InputProc;
    input.inputProcRefCon = 0;

    AudioUnitSetProperty(
            InputUnit, 
            kAudioOutputUnitProperty_SetInputCallback, 
            kAudioUnitScope_Global,
            0,
            &input,
            sizeof(input));
}

先頭に戻る

AudioOutputUnit の初期化と開始

AUHAL がデバイスから入力を受け取るように設定します。データの取得を開始するには、Audio Unit を初期化して開始する必要があります。

リスト 7. AUHAL の開始

OSStatus InitAndStartAUHAL()
{
   OSStatus err =noErr;

   err = AudioUnitInitialize(InputUnit);
   if(err) 
       return err;

   err = AudioOutputUnitStart(InputUnit);

   return err;
}

先頭に戻る

AudioOutputUnit からのデータの取得

AUHAL は、オーディオデバイスとの間でオーディオデータを送受信できる Audio Unit です。AUHAL からオーディオを受信するには、Audio Unit の出力範囲から取得する必要があります。実際には、これはクライアントが AudioUnitRender を呼び出すことにより行われます。オーディオを AUHAL に送信するには、入力範囲でデータを提供する必要があります。これは、入力コールバックを Audio Unit に提供することによって行われます。

下の例では、入力プロシージャ内から AudioUnitRender を呼び出します。入力プロシージャのレンダリングアクションフラグ、タイムスタンプ、バス番号、および必要なフレーム数を AudioUnitRender に伝達する必要があります。AudioBufferList、すなわち ioData は NULL になるため、独自に割り当てた AudioBufferList を提供しなければなりません。

リスト 8. AudioUnitRender を使ったデータの取得

AudioBufferList * theBufferList; 
/* バッファデータを保持するために割り当て */

OSStatus InputProc(
                    void *inRefCon,
                    AudioUnitRenderActionFlags *ioActionFlags,
                    const AudioTimeStamp *inTimeStamp,
                    UInt32 inBusNumber,
                    UInt32 inNumberFrames,
                    AudioBufferList * ioData)
{
    OSStatus err =noErr;

    err= AudioUnitRender(InputUnit,
                    ioActionFlags,
                    inTimeStamp, 
                    inBusNumber,     // 入力データの '1' になる
                    inNumberFrames, // 必要なフレーム数
                    theBufferList);

    return err;
}

先頭に戻る

まとめ

AUHAL を使ってオーディオデバイスとインタフェースをとると、アプリケーションとオーディオデバイス間の対話を大幅に簡素化できます。この Audio Unit は、オーディオデバイス情報の取得と、オーディオデータの転送や取得を行う際にオーディオデベロッパを支援します。

先頭に戻る

サンプルコード、参考資料、注意事項

サンプルコード - ComplexPlayThru
サンプルコード - SimplePlayThru
Core Audio Preliminary doc

Audio and MIDI on Mac OS X -May 2001

注意: システムの条件QuickTime v6.5 をインストールした. Mac OS X v10.2 および v10.3。

先頭に戻る

ドキュメントの改訂履歴

日付メモ
2004-08-23AUHAL サンプルコードプロジェクトへのリンクを追加。入出力有効化プロセスの訂正を含め、若干の変更。
2004-07-06ソースリストの若干の訂正。
2004-03-22改訂詳細不明。
2004-03-04新規ドキュメント。

掲載日: 2004-08-23